home *** CD-ROM | disk | FTP | other *** search
- #! /usr/bin/env python
-
- # XXX This only works on SGIs running IRIX 4.0 or higher
-
- # JUKEBOX: browse directories full of sampled sound files.
- #
- # One or more "list windows" display the files and subdirectories of
- # the arguments. Double-clicking on a subdirectory opens a new window
- # displaying its contents (and so on recursively). Double clicking
- # on a file plays it as a sound file (assuming it is one).
- #
- # Playing is asynchronous: the application keeps listening for events
- # while the sample is playing, so you can cancel playing or start a
- # new sample right away. Synchronous playing is available through the
- # -s option.
- #
- # The control window displays a "stop button" that cancel the current
- # play request.
- #
- # Most sound file formats recognized by SOX or SFPLAY are recognized.
- # Since conversion is costly, converted files are cached in
- # /usr/tmp/@j* until the user quits or changes the sampling rate via
- # the Rate menu.
-
- import commands
- import getopt
- import os
- from stat import *
- import rand
- import stdwin
- from stdwinevents import *
- import sys
- import tempfile
- import sndhdr
-
- from WindowParent import WindowParent
- from Buttons import PushButton
-
- # Pathnames
-
- DEF_DB = '/usr/local/sounds' # Default directory of sounds
- SOX = '/usr/local/bin/sox' # Sound format conversion program
- SFPLAY = '/usr/sbin/sfplay' # Sound playing program
-
-
- # Global variables
-
- class struct: pass # Class to define featureless structures
-
- G = struct() # Holds writable global variables
-
-
- # Main program
-
- def main():
- G.synchronous = 0 # If set, use synchronous audio.write()
- G.debug = 0 # If set, print debug messages
- G.busy = 0 # Set while asynchronous playing is active
- G.windows = [] # List of open windows, except control
- G.mode = '' # File type (default any that sfplay knows)
- G.rate = 0 # Sampling rate (default " " " ")
- G.tempprefix = tempfile.mktemp()
- #
- try:
- optlist, args = getopt.getopt(sys.argv[1:], 'dr:st:')
- except getopt.error, msg:
- sys.stdout = sys.stderr
- print msg
- print 'usage: jukebox [-d] [-s] [-t type] [-r rate]'
- print ' -d debugging (-dd event debugging)'
- print ' -s synchronous playing'
- print ' -t type file type'
- print ' -r rate sampling rate'
- sys.exit(2)
- #
- for optname, optarg in optlist:
- if optname == '-d':
- G.debug = G.debug + 1
- elif optname == '-r':
- G.rate = int(eval(optarg))
- elif optname == '-s':
- G.synchronous = 1
- elif optname == '-t':
- G.mode = optarg
- #
- if G.debug:
- for name in G.__dict__.keys():
- print 'G.' + name, '=', `G.__dict__[name]`
- #
- if not args:
- args = [DEF_DB]
- #
- G.cw = opencontrolwindow()
- for dirname in args:
- G.windows.append(openlistwindow(dirname))
- #
- #
- try:
- maineventloop()
- finally:
- clearcache()
- killchild()
-
- # Entries in Rate menu:
- rates = ['default', '7350', \
- '8000', '11025', '16000', '22050', '32000', '41000', '48000']
-
- def maineventloop():
- mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP
- while G.windows:
- try:
- type, w, detail = event = stdwin.getevent()
- except KeyboardInterrupt:
- killchild()
- continue
- if w == G.cw.win:
- if type == WE_CLOSE:
- return
- if type == WE_TIMER:
- checkchild()
- if G.busy:
- G.cw.win.settimer(1)
- elif type == WE_MENU:
- menu, item = detail
- if menu is G.ratemenu:
- clearcache()
- if item == 0:
- G.rate = 0
- else:
- G.rate = eval(rates[item])
- for i in range(len(rates)):
- menu.check(i, (i == item))
- else:
- G.cw.dispatch(event)
- else:
- if type == WE_DRAW:
- w.drawproc(w, detail)
- elif type in mouse_events:
- w.mouse(w, type, detail)
- elif type == WE_CLOSE:
- w.close(w)
- del w, event
- else:
- if G.debug > 1: print type, w, detail
-
- def checkchild():
- if G.busy:
- waitchild(1)
-
- def killchild():
- if G.busy:
- os.kill(G.busy, 9)
- waitchild(0)
-
- def waitchild(options):
- pid, sts = os.waitpid(G.busy, options)
- if pid == G.busy:
- G.busy = 0
- G.stop.enable(0)
-
-
- # Control window -- to set gain and cancel play operations in progress
-
- def opencontrolwindow():
- stdwin.setdefscrollbars(0, 0)
- cw = WindowParent().create('Jukebox', (0, 0))
- #
- stop = PushButton().definetext(cw, ' Stop ')
- stop.hook = stop_hook
- stop.enable(0)
- G.stop = stop
- #
- cw.realize()
- #
- G.ratemenu = cw.win.menucreate('Rate')
- for r in rates:
- G.ratemenu.additem(r)
- if G.rate == 0:
- G.ratemenu.check(0, 1)
- else:
- for i in len(range(rates)):
- if rates[i] == `G.rate`:
- G.ratemenu.check(i, 1)
- #
- return cw
-
- def stop_hook(self):
- killchild()
-
-
- # List windows -- to display list of files and subdirectories
-
- def openlistwindow(dirname):
- list = os.listdir(dirname)
- list.sort()
- i = 0
- while i < len(list):
- if list[i][0] == '.':
- del list[i]
- else:
- i = i+1
- for i in range(len(list)):
- fullname = os.path.join(dirname, list[i])
- if os.path.isdir(fullname):
- info = '/'
- else:
- try:
- size = os.stat(fullname)[ST_SIZE]
- info = `(size + 1023)/1024` + 'k'
- except IOError:
- info = '???'
- info = '(' + info + ')'
- list[i] = list[i], info
- width = maxwidth(list)
- # width = width + stdwin.textwidth(' ') # XXX X11 stdwin bug workaround
- height = len(list) * stdwin.lineheight()
- stdwin.setdefwinsize(width, min(height, 500))
- stdwin.setdefscrollbars(0, 1)
- w = stdwin.open(dirname)
- stdwin.setdefwinsize(0, 0)
- w.setdocsize(width, height)
- w.drawproc = drawlistwindow
- w.mouse = mouselistwindow
- w.close = closelistwindow
- w.dirname = dirname
- w.list = list
- w.selected = -1
- return w
-
- def maxwidth(list):
- width = 1
- for name, info in list:
- w = stdwin.textwidth(name + ' ' + info)
- if w > width: width = w
- return width
-
- def drawlistwindow(w, area):
- ## (left, top), (right, bottom) = area
- d = w.begindrawing()
- d.erase((0, 0), (1000, 10000))
- lh = d.lineheight()
- h, v = 0, 0
- for name, info in w.list:
- if info == '/':
- text = name + '/'
- else:
- text = name + ' ' + info
- d.text((h, v), text)
- v = v + lh
- showselection(w, d)
- d.close()
-
- def hideselection(w, d):
- if w.selected >= 0:
- invertselection(w, d)
-
- def showselection(w, d):
- if w.selected >= 0:
- invertselection(w, d)
-
- def invertselection(w, d):
- lh = d.lineheight()
- h1, v1 = p1 = 0, w.selected*lh
- h2, v2 = p2 = 1000, v1 + lh
- d.invert(p1, p2)
-
- def mouselistwindow(w, type, detail):
- (h, v), clicks, button = detail[:3]
- d = w.begindrawing()
- lh = d.lineheight()
- if 0 <= v < lh*len(w.list):
- i = v / lh
- else:
- i = -1
- if w.selected <> i:
- hideselection(w, d)
- w.selected = i
- showselection(w, d)
- d.close()
- if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0:
- setcursors('watch')
- name, info = w.list[i]
- fullname = os.path.join(w.dirname, name)
- if info == '/':
- if clicks == 2:
- G.windows.append(openlistwindow(fullname))
- else:
- playfile(fullname)
- setcursors('cross')
-
- def closelistwindow(w):
- G.windows.remove(w)
-
- def setcursors(cursor):
- for w in G.windows:
- w.setwincursor(cursor)
- G.cw.win.setwincursor(cursor)
-
-
- # Playing tools
-
- cache = {}
-
- def clearcache():
- for x in cache.keys():
- cmd = 'rm -f ' + cache[x]
- if G.debug: print cmd
- sts = os.system(cmd)
- if sts:
- print cmd
- print 'Exit status', sts
- del cache[x]
-
- validrates = (8000, 11025, 16000, 22050, 32000, 44100, 48000)
-
- def playfile(filename):
- killchild()
- try:
- tuple = sndhdr.what(filename)
- except IOError, msg:
- print 'Can\'t open', filename, msg
- stdwin.fleep()
- return
- raw = 0
- if tuple:
- mode, rate = tuple[:2]
- if rate == 0:
- rate = G.rate
- if rate == 0:
- rate = 8000
- else:
- mode = G.mode
- rate = G.rate
- if G.debug: print 'mode =', mode, 'rate =', rate
- if mode in ('au', 'aiff', 'wav', 'aifc', 'ul', 'ub', 'sb') and \
- rate in validrates:
- tempname = filename
- if mode in ('ul', 'ub', 'sb'):
- raw = 1
- elif cache.has_key(filename):
- tempname = cache[filename]
- else:
- tempname = G.tempprefix + `rand.rand()` + '.aiff'
- cmd = SOX
- if G.debug:
- cmd = cmd + ' -V'
- if mode <> '':
- cmd = cmd + ' -t ' + mode
- cmd = cmd + ' ' + commands.mkarg(filename)
- cmd = cmd + ' -t aiff'
- if rate not in validrates:
- rate = 32000
- if rate:
- cmd = cmd + ' -r ' + `rate`
- cmd = cmd + ' ' + tempname
- if G.debug: print cmd
- sts = os.system(cmd)
- if sts:
- print cmd
- print 'Exit status', sts
- stdwin.fleep()
- try:
- os.unlink(tempname)
- except:
- pass
- return
- cache[filename] = tempname
- if raw:
- pid = sfplayraw(tempname, tuple)
- else:
- pid = sfplay(tempname, [])
- if G.synchronous:
- sts = os.wait(pid, 0)
- else:
- G.busy = pid
- G.stop.enable(1)
- G.cw.win.settimer(1)
-
- def sfplayraw(filename, tuple):
- args = ['-i']
- type, rate, channels, frames, bits = tuple
- if type == 'ul':
- args.append('mulaw')
- elif type == 'ub':
- args = args + ['integer', '8', 'unsigned']
- elif type == 'sb':
- args = args + ['integer', '8', '2scomp']
- else:
- print 'sfplayraw: warning: unknown type in', tuple
- if channels > 1:
- args = args + ['channels', `channels`]
- if not rate:
- rate = G.rate
- if rate:
- args = args + ['rate', `rate`]
- args.append('end')
- return sfplay(filename, args)
-
- def sfplay(filename, args):
- if G.debug:
- args = ['-p'] + args
- args = [SFPLAY, '-r'] + args + [filename]
- if G.debug: print 'sfplay:', args
- pid = os.fork()
- if pid == 0:
- # Child
- os.execv(SFPLAY, args)
- # NOTREACHED
- else:
- # Parent
- return pid
-
- main()
-